﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using LightPersist.Exceptions;
using OpenMcdf;

namespace LightPersist.Storage
{
	public class BinaryStorage : BaseStorage
	{
	    CompoundFile _file;

        protected override void OnConnected()
        {
            base.OnConnected();
            if (!File.Exists(CurrentConectionString))
            {
                _file = new CompoundFile();
                _file.Save(CurrentConectionString);
                _file.Close();
            }
            _file = new CompoundFile(CurrentConectionString, UpdateMode.Update, true, true);
        }

		public override void CommitRemove(ObjectState obj)
		{
			if (this.IsExistsCore(obj))
			{
				_file.RootStorage.Delete(EntryName(obj));
			}
			else
			{
				throw new StorageException(StrErrObjDoesNotExists);
			}
		}

		static string EntryName(ObjectState obj)
		{
			var b64 = Convert.ToBase64String(obj.Identity.ToByteArray());
			var name = b64.Substring(0, b64.Length - 2).Replace('/', ' ');
			return name;
		}

		static Guid EntryId(string name)
		{
			var b64 = name.Replace(' ', '/');
			b64 += "==";
			var id = Convert.FromBase64String(b64);
			return new Guid(id);
		}

		public override void CommitInsert(ObjectState obj)
		{
			if (this.IsExistsCore(obj))
			{
				throw new StorageException(StrErrObjAlreadyExists);
			}
			else
			{
				var stream = _file.RootStorage.AddStream(EntryName(obj));
				var streamType = _file.RootStorage.AddStream(EntryName(obj) + "_t");
				streamType.SetData(Encoding.UTF8.GetBytes(obj.ClassMetainfo.Name));
				Save(obj, stream);
			}
		}

		public override void CommitUpdate(ObjectState obj)
		{
			if (this.IsExistsCore(obj))
			{
				var stream = _file.RootStorage.GetStream(EntryName(obj));
				Save(obj, stream);
			}
			else
			{
				throw new StorageException(StrErrObjDoesNotExists);
			}
		}

		void Save(ObjectState obj, CFStream stream) {
			using (var ms = new MemoryStream())
			{
				using (var sw = new StreamWriter(ms))
				{
					XmlStorage.SerializeObject(obj, sw);
				}

				stream.SetData(ms.ToArray());
			}

			_file.Commit(true);
		}

		public override void LoadObjectCore(ObjectState obj)
		{
			if (this.IsExistsCore(obj))
			{
				var name = EntryName(obj);
				var stream = _file.RootStorage.GetStream(name);
				var streamType = _file.RootStorage.GetStream(name + "_t");
				var data = streamType.GetData();
				obj.ClassMetainfoDetected = Encoding.UTF8.GetString(data, 0, data.Length);
				data = stream.GetData();
				XmlStorage.DeserializeObject(Encoding.UTF8.GetString(data, 0, data.Length), obj);
			}
			else
			{
				throw new StorageException(StrErrObjDoesNotExists);
			}
		}

		public override bool IsExistsCore(ObjectState obj)
		{
            try
            {
            	_file.RootStorage.GetStream(EntryName(obj));
            	return true;
			}
			catch (CFItemNotFound)
            {
            	return false;
            }
		}

		public override IEnumerable<Guid> GetObjectIDsCore()
		{
			return _file.RootStorage.Children.Select(item => EntryId(item.GetEntryName()));
		}
	}
}
